/*
 *  sniff232 - Simple RS232 sniffer
 *  
 *  Trivial program to sniff communications made between two RS232 devices
 *
 *  Computer <------> [port1] Computer with sniff232 [port2] <------> Device
 *
 *  Use at your own risk.
 *
 *  Cicuttin Matteo (C) 2007-2010 - matteo.cicuttin@gmail.com
 *  Released under BSD license.
 *
 *  To compile:
 *  gcc -O2 -Wall -pedantic -o sniff232 sniff232.c
 *
 *  History:
 *  v0.0 (11/07/2007) -		Created sniff232
 *  v0.1 (06/03/2007) -		Added some functionalities
 *
 */
 
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/select.h>
#include <signal.h>
#include <err.h>
#include <sysexits.h>

#include "hexdump.h"
#include "serial.h"

#define BUFSZ		32
#define HDSTEP		16
#define MAX(a,b) ( (a > b) ? a : b )

#define OBUF_ENABLE	0x01

typedef struct
{
    char    *progname;
    
    char    *port1_dev, *port2_dev;
	char	*port1_name, *port2_name;
    int     port1_fd, port2_fd;
    int     port1_spd, port2_spd;
    
    char    *dump_filename;
    FILE    *dump_stream;
	
	int		flags;
} config_t;


static int ctrlc;

void    ctrlc_handler(int);
void    usage(char *);
void    configure_port(int, int);
int     initialize(config_t *);
int     xfer(int, int, char *, int);
void    run_sniffer(config_t *);
void    cleanup(config_t *);

void
ctrlc_handler(int unused)
{
    printf("\rDetected CTRL-C, exiting.\n");
    ctrlc = 1;
}

void
usage(char *progname)
{
    printf("sniff232 v0.1 - (C) Matteo Cicuttin 2007-2010\n\n");
    printf("Usage: %s -P <port1> -p <port2> -S <port1 speed>\n\n", progname);
		   
    printf("Mandatory options:\n");       
    printf("\t-P <port1>: First serial port device\n");
    printf("\t-p <port2>: Second serial port device\n");
    printf("\t-S <port1 speed>: Speed of first serial port\n\n");
	printf("Other options:\n");
    printf("\t-s <port2 speed>: Speed of second serial port. This is not needed if\n"
           "\t                  you want the same speed on both ports\n");
	printf("\t-N <name>: Give an alternative name to port1\n");
    printf("\t-n <name>: Give an alternative name to port2\n");
    printf("\t-f <filename>: Use file <filename> instead of stdout to print data dump\n");
	printf("\t-b Enable output buffering\n");
}

int
initialize(config_t *cfg)
{   
    if ( (cfg->port1_dev == NULL) || (cfg->port2_dev == NULL) ||
         (cfg->port1_spd == 0)
       )
    {
        errx(EX_USAGE, "Insufficient arguments, try running with -h");
    }
    
    cfg->port1_fd = open_line(cfg->port1_dev, cfg->port1_spd);
    if (cfg->port1_fd == -1)
        errx(EX_OSERR, "Problem opening %s: %s", cfg->port1_dev, strerror(errno));
    
		
    cfg->port2_fd = open_line(cfg->port1_dev, cfg->port1_spd);
    if (cfg->port2_fd == -1)
    {
        close(cfg->port1_fd);
        errx(EX_OSERR, "Problem opening %s: %s", cfg->port2_dev, strerror(errno));
    }
	
	cfg->dump_stream = stdout;
	if (cfg->dump_filename)
	{
		cfg->dump_stream = fopen(cfg->dump_filename, "w");
		if (!cfg->dump_stream)
			errx(EX_OSERR, "fopen: %s", strerror(errno));
	}

    printf("%s ready to run (%s at %d bps, %s at %d bps)\n",
        cfg->progname, cfg->port1_name, cfg->port1_spd, cfg->port2_name, cfg->port2_spd);
	
    return 0;
}

inline int
xfer(int src_fd, int dst_fd, char *buf, int bufsz)
{
    int rd;
    rd = read(src_fd, buf, bufsz);
	if (rd == -1)
        return -1;

	if ( write(dst_fd, buf, rd) == -1 )
        return -1;
    
    return rd;
}

void
run_sniffer(config_t *cfg)
{
    fd_set readset;
    char buf[BUFSZ];
    int rd, err, maxfd;
	struct hexdump_state_t *state;
	struct timeval tv;
	
	int port1_needflush = 1, port2_needflush = 1;
	
	tv.tv_sec = 0;
	tv.tv_usec = 100000;
    
    maxfd = MAX(cfg->port1_fd, cfg->port2_fd) + 1;

	printf("%d %d\n", cfg->port1_fd, cfg->port2_fd);

	state = hexdump_init(HDSTEP, cfg->dump_stream);

	FD_ZERO(&readset);	
	while(!ctrlc)
	{
		FD_SET(cfg->port1_fd, &readset);
		FD_SET(cfg->port2_fd, &readset);
		
		err = select(maxfd, &readset, NULL, NULL, &tv);
		if ( err < 0 )
		{
			if ( errno == EINTR )
			    continue;
			    
			cleanup(cfg);
			errx(EX_OSERR, "select: %s", strerror(errno));
		}
		
		if ( err == 0 )
		{
			hexdump_flush(state);
		}
		
		/**** BAD CODE BEGIN ****/
		
		if (FD_ISSET(cfg->port1_fd, &readset))
		{
			FD_CLR(cfg->port1_fd, &readset);
            rd = xfer(cfg->port1_fd, cfg->port2_fd, buf, BUFSZ);
            if (err < 0)
            {
                cleanup(cfg);
                errx(EX_OSERR, "xfer: %s\n", strerror(errno));
            }
			
			if (cfg->flags & OBUF_ENABLE)
			{
				port2_needflush = 1;
				if (port1_needflush)
				{
					port1_needflush = 0;
					hexdump_flush(state);
					fprintf(cfg->dump_stream, "%s -> %s\n", cfg->port1_name, cfg->port2_name);
				}
				buffered_hexdump(buf, rd, state);
			}
			else
			{
				fprintf(cfg->dump_stream, "%s -> %s\n", cfg->port1_name, cfg->port2_name);
				hexdump(buf, rd, HDSTEP, cfg->dump_stream);
			}
		}
		
		
		if (FD_ISSET(cfg->port2_fd, &readset))
		{
			FD_CLR(cfg->port2_fd, &readset);
            rd = xfer(cfg->port2_fd, cfg->port1_fd, buf, BUFSZ);
            if (err < 0)
            {
                cleanup(cfg);
                errx(EX_OSERR, "xfer: %s\n", strerror(errno));
            }
			
			if (cfg->flags & OBUF_ENABLE)
			{
				port1_needflush = 1;
				if (port2_needflush)
				{
					port2_needflush = 0;
					hexdump_flush(state);
					fprintf(cfg->dump_stream, "%s -> %s\n", cfg->port2_name, cfg->port1_name);
				}
				buffered_hexdump(buf, rd, state);
			}
			else
			{
				fprintf(cfg->dump_stream, "%s -> %s\n", cfg->port2_name, cfg->port1_name);
				hexdump(buf, rd, HDSTEP, cfg->dump_stream);
			}
		}
		
		/**** BAD CODE END ****/
	}
	
	hexdump_free(state);
}

void
cleanup(config_t *cfg)
{
    close(cfg->port1_fd);
    close(cfg->port2_fd);
	if (cfg->dump_filename)
		fclose(cfg->dump_stream);
}

int
main(int argc, char **argv)
{	
	int ch;
	struct sigaction sa;
	config_t cfg;
	
	bzero(&cfg, sizeof(config_t));
	cfg.progname = argv[0];
	
	while ( (ch = getopt(argc, argv, "P:p:S:s:N:n:f:hb")) != -1)
	{
	    switch (ch)
	    {
	        case 'P':
	            cfg.port1_dev = optarg;
				if (cfg.port1_name == NULL)
					cfg.port1_name = optarg;
	            break;
	        
	        case 'p':
	            cfg.port2_dev = optarg;
				if (cfg.port2_name == NULL)
					cfg.port2_name = optarg;
	            break;
	            
	        case 'S':
	            cfg.port1_spd = atoi(optarg);
	            if (cfg.port2_spd == 0)
	                cfg.port2_spd = cfg.port1_spd;
	            break;
	            
	        case 's':
	            cfg.port2_spd = atoi(optarg);
	            break;
				
			case 'N':
				cfg.port1_name = optarg;
				break;
				
			case 'n':
				cfg.port2_name = optarg;
				break;
	            
	        case 'f':
	            cfg.dump_filename = optarg;
	            break;
	        
	        case 'h':
	            usage(argv[0]);
	            exit(0);
	            break;
			
			case 'b':
				cfg.flags |= OBUF_ENABLE;
				break;
				
	        case '?':
	        default:
	            errx(EX_USAGE, "Bad arguments on command line, try running with -h");
	            break;
	    }
	}
	
	ctrlc = 0;
	sa.sa_handler = ctrlc_handler;
	sa.sa_flags = 0;
	
	if (sigaction(SIGINT, &sa, NULL) < 0)
    {
        cleanup(&cfg);
        errx(EX_OSERR, "%s", strerror(errno));
    }
	
	initialize(&cfg);
	run_sniffer(&cfg);
	cleanup(&cfg);
	
	return 0;
}

